package com.lizonghui.googleplay.manager;

import com.lizonghui.googleplay.bean.DownLoadInfo;
import com.lizonghui.googleplay.bean.ItemInfoBean;
import com.lizonghui.googleplay.config.Constants;
import com.lizonghui.googleplay.factory.ThreadPoolProxyFactory;
import com.lizonghui.googleplay.utils.CommonUtils;
import com.lizonghui.googleplay.utils.FileUtils;
import com.lizonghui.googleplay.utils.HttpUtils;
import com.lizonghui.googleplay.utils.IOUtils;
import com.lizonghui.googleplay.utils.UIUtils;
import com.squareup.okhttp.OkHttpClient;
import com.squareup.okhttp.Request;
import com.squareup.okhttp.Response;

import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * Created by Administrator on 2016/6/27.
 * 下载管理类，负责下载模块，封装所有和下载相关的逻辑
 * 1需要时刻记录当前状态
 * 2更具传递过来的detailInfoBean.返回downloadInfo
 * 3当状态改变时候，实时通知UI。-->发布消息-->被观察者
 * 根据各种情况，触发不同的操作
 */
public class DownLoadManger {
    private static DownLoadManger instance;


    /*-------------1定义常量，记录各种状态----------------------*/
    public static final int STATE_UNDOWNLOAD = 0; //未下载
    public static final int STATE_DOWNLOADING = 1;   //下载中
    public static final int STATE_PAUSEDOWNLOAD = 2;   //暂停下载
    public static final int STATE_WAITINGDOWNLOAD = 3;   //等待下载
    public static final int STATE_DOWNLOADFAILED = 4;   //下载失败
    public static final int STATE_DOWNLOADED = 5;   //下载完成
    public static final int STATE_INSTALLED = 6;   //已安装

// 由于每个进来的item都有自己的状态，所以这个状态应该放到下载信息类中
//  public int mCurrentState = STATE_UNDOWNLOAD;//默认未下载

    /**
     * 保存所有的，凡是用户点击下载按钮后的，开始下载的一些任务
     * 因为会有很多的应用被点击，有的下载失败，有的下载中，有的下载完成
     */
    private Map<String, DownLoadInfo> mCacheDownLoadInfoMap = new HashMap<>();
    private String mSavePath;
    private File mSaveFile;

    private DownLoadManger() {
    }

    public static DownLoadManger getInstance() {
        if (instance == null) {
            synchronized (DownLoadManger.class) {
                if (instance == null) {
                    instance = new DownLoadManger();
                }
            }
        }
        return instance;
    }

    /**
     * @param downLoadInfo
     * @des 根据url和apk保存路径下载文件
     * @call 当用户点击下载按钮时候
     * String downloadUrl, File saveApk, long size
     */
    public void doDownLoad(DownLoadInfo downLoadInfo) {
        //用户点击了，将下载信息保存到集合中
        mCacheDownLoadInfoMap.put(downLoadInfo.packageName, downLoadInfo);


        //一个任务加载到线程池，我们不知道任务是在执行，还是在缓存队列
        /**
         * 预先把状态设置为等待状态：
         *  1.假如任务立马执行-->立刻切换到下载中，，，
         *  2.假如任务没有执行-->保持等待下载的状态
         */
         /*#######################等待下载############################*/
        downLoadInfo.currentState = STATE_WAITINGDOWNLOAD;
        //downLoadInfo里面状态改变了，通知所有观察者
        notifyObservers(downLoadInfo);
        /*##########################################################*/

        //异步下载
        DownLoadTask downLoadTask = new DownLoadTask(downLoadInfo);
        downLoadInfo.downLoadTask = downLoadTask;//记录当前任务到下载信息中
        ThreadPoolProxyFactory.createDownLoadThreadPoolProxy().execute(downLoadTask);//这些都是下载相关的，我们交给下载类去实现

    }


    class DownLoadTask implements Runnable {
        private DownLoadInfo downLoadInfo;

        public DownLoadTask(DownLoadInfo downLoadInfo) {
            this.downLoadInfo = downLoadInfo;
        }

        @Override
        public void run() {
            /**********断电继续下载***1注意开始下载位值变化,range和UI中的progress**********************/
            //初始下载位置，应该是部分文件大小
            long initRange = 0;

            File saveAPK = new File(downLoadInfo.savePath);
            if (saveAPK.exists()) {
                initRange = saveAPK.length();
            }
            //处理上一次的UI进度条
            downLoadInfo.progress = initRange;

            /**********断电继续下载***1注意开始下载位值变化，range和UI中的progress**********************/

            //真正在子线程中执行下载任务
            //2下载后-->文件放哪里，文件命名（唯一标识）
            InputStream mIn = null;
            BufferedOutputStream mBufo = null;
            try {
                //创建Http对象
                OkHttpClient okHttpClient = new OkHttpClient();

                // 拼接url，JSON返回的是app/com.itheima.www/com.itheima.www.apk
                //  总URL： http://localhost:8080/GooglePlayServer/download?name=app/com.itheima.www/com.itheima.www.apk  & range=0
                //用参数集合存储这些子地址
                Map<String, Object> urlParams = new HashMap<>();
                urlParams.put("range", initRange + "");/***断电继续下载的range变化****/
                urlParams.put("name", downLoadInfo.downloadUrl);
                String downloadUrl = HttpUtils.getUrlParamsByMap(urlParams);//这里注意后放的键值对出现在前面
                downloadUrl = Constants.URLS.DOWNLOADBASEURL + downloadUrl;


                //创建请求对象
                Request request = new Request.Builder().get().url(downloadUrl).build();
                //发起请求，由于这里已经是子线程，用同步请求就可以
                Response response = okHttpClient.newCall(request).execute();
                //拿到返回数据，先判断是否成功
                if (response.isSuccessful()) {
                    mIn = response.body().byteStream();

                    //inputStream-->File
//                    mBufo = new BufferedOutputStream(new FileOutputStream(mSavePath));
                    mBufo = new BufferedOutputStream(new FileOutputStream(saveAPK, true));/**断电继续下载，用追加写的方式***/
                    byte[] buf = new byte[1024];
                    int len = -1;
                    boolean isPause = false;//添加暂停逻辑的判断位
                    while ((len = mIn.read(buf)) != -1) {
                        //判断是否点击了暂停下载
                        if (downLoadInfo.currentState == STATE_PAUSEDOWNLOAD) {
                            //停止读写操作
                            isPause = true;
                            break;
                        }

                        /*#######################下载中#############################*/
                        downLoadInfo.currentState = STATE_DOWNLOADING;
                        //动态设置下载的进度，每写一次就更改UI，所以要放到循环里，不断刷新UI
                        downLoadInfo.progress += len;
                        //downLoadInfo里面状态改变了，通知所有观察者
                        notifyObservers(downLoadInfo);
                       /*##########################################################*/
                        mBufo.write(buf, 0, len);

                        //使用okHttpClient进行文件读写时候加上如下代码
                        if (mSaveFile.length() == downLoadInfo.size) {
                        //写完了，主动跳出,因为在okHttp中，如果返回-1，就会抛出异常的
                            break;
                        }

                    }
                    //while之后到之类，这里可能是读写完成，也可能是暂停跳出来的。所有要加判断位
                    if (isPause) {
                        //暂停中，不做处理
                    } else {
                    /*#######################下载完成############################*/
                        downLoadInfo.currentState = STATE_DOWNLOADED;
                        //downLoadInfo里面状态改变了，通知所有观察者
                        notifyObservers(downLoadInfo);
                    /*##########################################################*/
                    }

                } else {
                    //下载失败
                    return;
                }
            } catch (IOException e) {
                e.printStackTrace();
                /*#######################下载失败############################*/
                downLoadInfo.currentState = STATE_DOWNLOADFAILED;
                //downLoadInfo里面状态改变了，通知所有观察者
                notifyObservers(downLoadInfo);
                /*##########################################################*/
            } finally {
                IOUtils.close(mBufo);
                IOUtils.close(mIn);
            }
        }
    }

    /**
     * @param data
     * @des 根据传递进来的的DetailInfoBean信息，返回一个downLoadInfo对象
     */
    public DownLoadInfo getDownLoadInfo(ItemInfoBean data) {

        /**
         * 1未下载
         * 2下载中
         * 3暂停下载
         * 4等待下载
         * 5下载失败
         * 6下载完成
         * 7已安装
         */

        //放到哪里
        //文件命名
        String dir = FileUtils.getDir("apk");//创建目录sdcard/android/data/包目录/apk
        String fileName = data.packageName + ".apk";//创建文件名
        mSaveFile = new File(dir, fileName);
        mSavePath = mSaveFile.getAbsolutePath();

        DownLoadInfo downLoadInfo = new DownLoadInfo();
        downLoadInfo.savePath = mSavePath;
        downLoadInfo.packageName = data.packageName;
        downLoadInfo.size = data.size;
        downLoadInfo.downloadUrl = data.downloadUrl;

        //这里要注意顺序，从低到高（不然安装了，还是显示下载完成的。因为安装是比完成更进一步的操作，凡是安装了，都是已下载，如果已下载在前面，就会一直显示已下载）
        //已安装
        if (CommonUtils.isInstalled(UIUtils.getContext(), data.packageName)) {
            downLoadInfo.currentState = STATE_INSTALLED;
            return downLoadInfo;
        }
        //下载完成
        if (mSaveFile != null && mSaveFile.length() == data.size) {
            downLoadInfo.currentState = STATE_DOWNLOADED;
            return downLoadInfo;
        }


        //说明，data对应的的downLoadInfo，用户肯定点击了下载按钮进行触发下载,确又没有到达完成和安装状态
        if (mCacheDownLoadInfoMap.containsKey(data.packageName)) {
            downLoadInfo = mCacheDownLoadInfoMap.get(data.packageName);
            return downLoadInfo;
        }

        //未下载
        return downLoadInfo;
    }


    /*===============自己实现观察者设计模式，发布最新的状态（DownLoadInfo） 一个状态变化，多个UI获取其变化===================*/
    //1定义接口和方法----->状态改变之后，UI根据改变的状态干嘛，不管。实现这个接口，就是能够获得并根据我改变的状态，做出响应
    public interface DownLoadInfoObserver {
        void onDownLoadInfoChanged(DownLoadInfo downLoadInfo);
    }

    //2定义接口对象的集合,也就是所有的观察者
    public List<DownLoadInfoObserver> mObservers = new ArrayList<>();

    //3添加观察者
    public synchronized void addObserver(DownLoadInfoObserver o) {
        if (o == null) {
            throw new NullPointerException();
        }
        if (!mObservers.contains(o)) {
            mObservers.add(o);
        }
    }
    //4移除观察者

    public synchronized void deleteObserver(DownLoadInfoObserver o) {
        mObservers.remove(o);
    }

    //5通知所有观察者-->发布消息
    public synchronized void notifyObservers(DownLoadInfo downLoadInfo) {
        for (DownLoadInfoObserver o : mObservers) {
            o.onDownLoadInfoChanged(downLoadInfo);
        }
    }
    /*===============自己实现观察者设计模式，发布最新的状态（DownLoadInfo）===================*/

    /**
     * @param downLoadInfo
     * @des 暂停下载
     * @call 如果现在正在下载，用户点击了下载按钮
     */
    public void pauseDownLoad(DownLoadInfo downLoadInfo) {
         /*#######################当前状态：暂停下载############################*/
        downLoadInfo.currentState = STATE_PAUSEDOWNLOAD;
        //downLoadInfo里面状态改变了，通知所有观察者
        notifyObservers(downLoadInfo);
        /*##########################################################*/
    }

    /**
     * @param downLoadInfo
     * @des 取消下载
     * @call 当前热舞怒在缓存队列中的时候，用户点击了下载按钮
     */
    public void cancelDownLoad(DownLoadInfo downLoadInfo) {
          /*#######################当前状态：######取消下载######################*/
        downLoadInfo.currentState = STATE_UNDOWNLOAD;
        //downLoadInfo里面状态改变了，通知所有观察者
        notifyObservers(downLoadInfo);
        /*##########################################################*/
        //在缓存队列时候移除
        ThreadPoolProxyFactory.createDownLoadThreadPoolProxy().remove(downLoadInfo.downLoadTask);
    }
}
